﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Esponce.QRCode.Content
{
	internal sealed class ContentFormatHelper
	{
		/// <summary>
		/// Gets an inline format name, e.g. "MECARD:N:John;;" -> "MECARD".
		/// Suitable for MECARD, WIFI and other inline formats.
		/// </summary>
		/// <param name="content">Raw content to extract format name from.</param>
		/// <param name="convertToUpperCase">A value indicating whether to convert tag name letters to upper case.</param>
		/// <returns>Returns a tag name or null if tag name cannot be found.</returns>
		public string GetInlineTagName(string content, bool convertToUpperCase = true)
		{
			//Check if content is set
			if (string.IsNullOrEmpty(content))
			{
				return null;
			}

			//Parse tag name, e.g. "MECARD:N:John;;" -> "MECARD"
			var m = Regex.Match(content, @"^\s*(?<name>[\w\d;]+):", RegexOptions.IgnoreCase | RegexOptions.Singleline);
			if (!m.Success)
			{
				return null;
			}

			return (convertToUpperCase ? m.Groups["name"].Value.ToUpper() : m.Groups["name"].Value).Trim();
		}

		/// <summary>
		/// Gets a block format name, e.g. "BEGIN:VCARD" -> "VCARD".
		/// Suitable for VCARD, VCALENDAR and other formats with BEGIN: and END: and new lines as separator.
		/// </summary>
		/// <param name="content">Raw content to extract format name from.</param>
		/// <param name="convertToUpperCase">A value indicating whether to convert tag name letters to upper case.</param>
		/// <returns>Returns a tag name or null if tag name cannot be found.</returns>
		public string GetBlockTagName(string content, bool convertToUpperCase = true)
		{
			//Check if content is set
			if (string.IsNullOrEmpty(content))
			{
				return null;
			}

			//Parse tag name, e.g. "BEGIN:VCARD..." -> "VCARD"
			var m = Regex.Match(content, @"^\s*BEGIN:(?<begin>[\w\d]+)(.*)END:(?<end>[\w\d]+)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
			if (!m.Success)
			{
				return null;
			}

			return (convertToUpperCase ? m.Groups["begin"].Value.ToUpper() : m.Groups["begin"].Value).Trim();
		}

		/// <summary>
		/// Parses inline content, collects tag name and key-value pairs.
		/// Suitable for MECARD, WIFI and other inline formats.
		/// </summary>
		/// <param name="content">Raw content to be parsed.</param>
		/// <param name="convertToUpperCase"></param>
		/// <param name="ignoreEmptyValues"></param>
		/// <returns></returns>
		public ContentFormatDictionary ParseInline(string content, bool convertToUpperCase = true, bool ignoreEmptyValues = true)
		{
			//Check if content is set
			if (string.IsNullOrEmpty(content))
			{
				throw new ArgumentException("Content is missing!");
			}

			var result = new ContentFormatDictionary();

			//Parse tag name, e.g. "MECARD:N:John;;" -> "MECARD"
			var m = Regex.Match(content, @"^\s*(?<name>[\w\d]+):", RegexOptions.IgnoreCase | RegexOptions.Singleline);
			if (!m.Success)
			{
				throw new ArgumentException("Tag name is missing!");
			}

			result.Name = (convertToUpperCase ? m.Groups["name"].Value.ToUpper() : m.Groups["name"].Value).Trim();

			//Trim tag name prefix
			string prefix = result.Name + ":"; //Example: "MECARD:"
#if WINRT
			int pos = content.IndexOf(prefix, StringComparison.CurrentCultureIgnoreCase);
#else
			int pos = content.IndexOf(prefix, StringComparison.InvariantCultureIgnoreCase);
#endif
			content = content.Substring(pos + prefix.Length);

			//Some generators ignore ';' at the end to save space
			if (!content.EndsWith(";"))
			{
				content += ";";
			}

			//Parse key-value pairs
			var matches = Regex.Matches(content, @"((?<key>[\w\d]+):(?<value>[^;]+);)");
			foreach (Match match in matches)
			{
				string key = (convertToUpperCase ? match.Groups["key"].Value.ToUpper() : match.Groups["key"].Value);
				string value = match.Groups["value"].Value;

				if (ignoreEmptyValues && string.IsNullOrEmpty(value))
				{
					continue;
				}

				result.Add(key, value);
			}

			return result;
		}

		/// <summary>
		/// Builds inline content from tag name and key-value pairs.
		/// </summary>
		/// <param name="dictionary"></param>
		/// <param name="convertToUpperCase"></param>
		/// <param name="ignoreEmptyValues"></param>
		/// <returns></returns>
		public string BuildInline(ContentFormatDictionary dictionary, bool convertToUpperCase = true, bool ignoreEmptyValues = true)
		{
			//Check if dictionary is set
			if (dictionary == null)
			{
				throw new ArgumentException("Content is missing!");
			}
			if (string.IsNullOrEmpty(dictionary.Name))
			{
				throw new ArgumentException("Tag name is missing!");
			}

			//Build tag name
			var builder = new StringBuilder();
			string tagName = (convertToUpperCase ? dictionary.Name.ToUpper() : dictionary.Name).Trim();
			builder.Append(tagName).Append(":");

			//Build key-value pairs
			foreach (var pair in dictionary)
			{
				if (ignoreEmptyValues && string.IsNullOrEmpty(pair.Value))
				{
					continue;
				}

				string key = (convertToUpperCase ? pair.Key.ToUpper() : pair.Key).Trim();
				builder.Append(key).Append(":").Append(pair.Value).Append(";");
			}

			//Finish the tag
			string result = builder.Append(";").ToString();
			return result;
		}

		/// <summary>
		/// Encodes new line characters to "=0D=0A" (0x0D = '\r', 0x0A = '\n')
		/// </summary>
		/// <param name="multiline"></param>
		/// <returns></returns>
		public static string EncodeNewLine(string multiline)
		{
			if (string.IsNullOrEmpty(multiline))
			{
				return multiline;
			}

			string result = multiline;
			result = result.Replace("\r\n", "\n").Replace("\n\r", "\n").Replace("\r", "\n");
			result = result.Replace("\n", "=0D=0A");
			return result;
		}

		/// <summary>
		/// Decodes new line characters to "\r\n" (0x0D = '\r', 0x0A = '\n')
		/// </summary>
		/// <param name="multiline"></param>
		/// <returns></returns>
		public static string DecodeNewLine(string multiline)
		{
			if (string.IsNullOrEmpty(multiline))
			{
				return multiline;
			}

			string result = multiline;
			result = result.Replace("=0D", "\r").Replace("=0d", "\r");
			result = result.Replace("=0A", "\n").Replace("=0a", "\n");
			return result;
		}

		/// <summary>
		/// Add special characters to escape sequence notation, e.g. ":" goes to "¥:"
		/// </summary>
		/// <returns></returns>
		/// <remarks>
		/// See note in Common Items documentation
		/// http://www.nttdocomo.co.jp/english/service/developer/make/content/barcode/function/application/common/index.html
		/// </remarks>
		public static string Escape(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return value;
			}

			value = value.Replace("¥", "¥¥");
			value = value.Replace(":", "¥:");
			value = value.Replace(";", "¥;");
			value = value.Replace(",", "¥,");
			return value;
		}

		/// <summary>
		/// Removes escape sequence notation, e.g. "¥:" goes to ":" in "URL:http¥://i.nttdocomo.co.jp/;"
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		/// <remarks>
		/// See note in Common Items documentation
		/// http://www.nttdocomo.co.jp/english/service/developer/make/content/barcode/function/application/common/index.html
		/// </remarks>
		public static string Unescape(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return value;
			}

			value = value.Replace("¥¥", "¥");
			value = value.Replace("¥:", ":");
			value = value.Replace("¥;", ";");
			value = value.Replace("¥,", ",");
			return value;
		}

		/// <summary>
		/// Gets array from string.
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static string[] Split(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return new string[] { value };
			}

			//Replace escaped value with temporary symbol
			const string SYMBOL = "<SYMBOL>";
			value = value.Replace("¥,", SYMBOL);

			string[] result = value.Split(',');
			for (int i = 0; i < result.Length; i++)
			{
				//Replace symbol back to original value
				result[i] = result[i].Replace(SYMBOL, "¥,");
			}

			return result;
		}
	}

	internal class ContentFormatDictionary : Dictionary<string, string>
	{
		/// <summary>
		/// Gets or sets a tag name, e.g. "MECARD"
		/// </summary>
		public string Name
		{
			get;
			set;
		}
	}
}
